/*
 * 
* Copyright (C) 2004-2006  Autodesk, Inc.
* 
* This library is free software; you can redistribute it and/or
* modify it under the terms of version 2.1 of the GNU Lesser
* General Public License as published by the Free Software Foundation.
* 
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
* 
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
* 
 */

#include "stdafx.h"
#include "MapInfoProvider.h"
#include "MapInfoApplySchema.h"
#include "OgrFdoUtil.h"
#include "MapInfoFeatureSchema.h"
#include "MapInfoClassDefinition.h"
#include "MapInfoSpatialContext.h"

#include <FdoCommonStringUtil.h>
#include <FdoCommonSchemaUtil.h>
#include <FdoCommonFile.h>

#include <mitab.h>
#include <mitab_utils.h>

MapInfoApplySchema::MapInfoApplySchema (MapInfoConnection *connection) :
    MapInfoCommand<FdoIApplySchema> (connection),
    m_ignoreStates (false)
{
}

/** Do not implement the copy constructor. **/
//MapInfoApplySchema::MapInfoApplySchema (const MapInfoApplySchema &right) { }

MapInfoApplySchema::~MapInfoApplySchema (void)
{
}

/** Do not implement the assignment operator. **/
//MapInfoApplySchema & operator= (const MapInfoApplySchema &right);

FdoFeatureSchema* MapInfoApplySchema::GetFeatureSchema ()
{
    return (FDO_SAFE_ADDREF (m_schema.p));
}

void MapInfoApplySchema::SetFeatureSchema (FdoFeatureSchema* value)
{
    m_schema = FDO_SAFE_ADDREF (value);
}

FdoPhysicalSchemaMapping* MapInfoApplySchema::GetPhysicalMapping ()
{
    return NULL;
}

void MapInfoApplySchema::SetPhysicalMapping (FdoPhysicalSchemaMapping* value)
{
    throw FdoCommandException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_PHYSICAL_SCHEMA_MAPPING_NOT_SUPPORTED)));
}

FdoBoolean MapInfoApplySchema::GetIgnoreStates()
{
    return (m_ignoreStates);
}

void MapInfoApplySchema::SetIgnoreStates( FdoBoolean ignoreStates )
{
    m_ignoreStates = ignoreStates;
}


//
// internals
//
void MapInfoApplySchema::AddSchema ()
{
    // MapInfo provider can't create a new schema. 
    // However, we can add new feature class into the existing schema.
    
    FdoString * name = m_schema->GetName();
    if(!name || !name[0])
    {
        throw FdoCommandException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_SCHEMA_NOT_SPECIFIED)));
    }

    if(!wcscmp(name, TAB_SCHEMA_NAME) ||!wcscmp(name, MIF_SCHEMA_NAME))
    {
        ModifySchema();
    }
    else
    {
        throw FdoCommandException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_ADD_SCHEMA)));
    }
}

void MapInfoApplySchema::ModifySchema ()
{
    FdoString* schemaName = m_schema->GetName();
    FdoPtr<MapInfoFeatureSchemaCollection> mapinfoSchemas = m_connection->GetMapInfoSchemas();
    FdoPtr<MapInfoFeatureSchema> mapinfoSchema = mapinfoSchemas->FindItem (schemaName);
    if (mapinfoSchema.p == NULL)
    {
        throw FdoCommandException::Create(NlsMsgGetMain(MAPINFOPROVIDER_SCHEMA_NOT_EXIST, "Schema '%1$ls' doesn't exist.", schemaName));
    }

    FdoPtr<MapInfoClassDefinitionCollection> mapinfoClasses = mapinfoSchema->GetMapInfoClasses();
    FdoPtr<FdoClassCollection> fdoClasses = m_schema->GetClasses();
    for (int i = 0; i < fdoClasses->GetCount(); ++i)
    {
        FdoPtr<FdoClassDefinition> fdoClassDef = fdoClasses->GetItem(i);
        FdoPtr<MapInfoClassDefinition> mapinfoClassDef = mapinfoClasses->FindItem (fdoClassDef->GetName ());

        // Handle IgnoreStates flag:
        FdoSchemaElementState state = fdoClassDef->GetElementState ();
        if (GetIgnoreStates ())
        {
            if (mapinfoClassDef == NULL)
                state = FdoSchemaElementState_Added;
            else
                state = FdoSchemaElementState_Modified;
        }
        switch (state)
        {
            // Indicates the schema element has been added.
            case FdoSchemaElementState_Added:
                mapinfoSchema->AddClass(fdoClassDef);
                break;

            // Indicates the schema element is marked for deletion.
            case FdoSchemaElementState_Deleted:
                mapinfoSchema->DeleteClass(mapinfoClassDef);
                break;

            // Indicates the schema element is not associated with an FdoFeatureSchema.
            case FdoSchemaElementState_Detached:
                // duh, waddo I do
                break;

            // Indicates the schema element has been modified in some way.
            case FdoSchemaElementState_Modified:
                mapinfoSchema->ModifyClass(fdoClassDef);
                break;

            // Indicates the schema element has not been changed.
            case FdoSchemaElementState_Unchanged:
                break;

            default:
                throw FdoException::Create (NlsMsgGetMain(MAPINFOPROVIDER_SCHEMA_UNSUPPORTED_ELEMENT_STATE, "Schema element state '%1$d' is not supported.", (int)state));
        }
    }
}

void MapInfoApplySchema::DeleteSchema ()
{
    FdoString* schemaName = m_schema->GetName();
    FdoPtr<MapInfoFeatureSchemaCollection> mapinfoSchemas = m_connection->GetMapInfoSchemas();
    FdoPtr<MapInfoFeatureSchema> mapinfoSchema = mapinfoSchemas->FindItem (schemaName);
    if (mapinfoSchema.p == NULL)
    {
        throw FdoCommandException::Create(NlsMsgGetMain(MAPINFOPROVIDER_SCHEMA_NOT_EXIST, "Schema '%1$ls' doesn't exist.", schemaName));
    }
    mapinfoSchema->DeleteAllClasses();
}

void MapInfoApplySchema::Execute ()
{
    // verify connection
    if (m_connection == NULL)
        throw FdoCommandException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_NO_CONNECTION)));

    if (m_connection->GetConnectionState() != FdoConnectionState_Open)
        throw FdoCommandException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_CONNECTION_CLOSED)));

    if (m_schema == NULL)
        throw FdoCommandException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_NULL_REFERENCE)));

    // Make sure all data property default values are valid.
    FdoCommonSchemaUtil::ValidateFdoFeatureSchema (m_schema);

    // Handle IgnoreStates flag:
    FdoPtr<MapInfoFeatureSchemaCollection> mapinfoSchemas = m_connection->GetMapInfoSchemas();
    FdoSchemaElementState state = m_schema->GetElementState ();
    if (GetIgnoreStates ())
    {
       FdoPtr<MapInfoFeatureSchema> mapinfoSchema = mapinfoSchemas->FindItem (m_schema->GetName ());
       if (mapinfoSchema == NULL)
           state = FdoSchemaElementState_Added;
       else
           state = FdoSchemaElementState_Modified;
    }

    switch (state)
    {
        // Indicates the schema element has been added.
        case FdoSchemaElementState_Added:
            AddSchema ();
            break;

        // Indicates the schema element is marked for deletion.
        case FdoSchemaElementState_Deleted:
            DeleteSchema ();
            break;

        // Indicates the schema element is not associated with an FdoFeatureSchema.
        case FdoSchemaElementState_Detached:
            // duh, waddo I do
            break;

        // Indicates the schema element has been modified in some way.
        case FdoSchemaElementState_Modified:
            ModifySchema ();
            break;

        // Indicates the schema element has not been changed.
        case FdoSchemaElementState_Unchanged:
            break;

        default:
            throw FdoException::Create (NlsMsgGetMain(MAPINFOPROVIDER_SCHEMA_UNSUPPORTED_ELEMENT_STATE, "Schema element state '%1$d' is not supported.", (int)state));
    }

    // mark the schema as "unchanged"
    m_schema->AcceptChanges ();

    FdoPtr<FdoFeatureSchemaCollection> temp1 = m_connection->DescribeSchema(true);
    FdoPtr<MapInfoSpatialContextCollection> temp2 = m_connection->GetSpatialContexts(true);
}

// From MapInfo, property name can contain only letters, numbers, and '_'.
// It cann't contain spaces or punctuation marks, and it cannot begin with a number.
// The length of property name can't be longer than the max of 31 characters.
void MapInfoApplySchema::ValidateProperty (FdoFeatureSchema * schema)
{
    if (schema != NULL) 
    {
        FdoPtr<FdoClassCollection> classes = schema->GetClasses();
        if (classes != NULL) 
        {
            for (FdoInt32 i=0; i<classes->GetCount(); i++) 
            {
                FdoPtr<FdoClassDefinition> pClass = classes->GetItem(i);
                if (pClass != NULL)
                {
                    FdoPtr<FdoPropertyDefinitionCollection> props = pClass->GetProperties();
                    if (props != NULL) 
                    {
                        for (FdoInt32 i=0; i<props->GetCount(); i++) 
                        {
                            FdoPtr<FdoPropertyDefinition> propDef = props->GetItem(i);
                            if (propDef != NULL)
                            {
                                FdoString* propName = propDef->GetName();
                                if (wcslen(propName) > 31)
                                    throw FdoCommandException::Create(NlsMsgGetMain(MAPINFOPROVIDER_EXCEED_MAX_LENGTH_OF_PROPERTY_NAME, "Invalid property name: %1$ls. Property name is longer than the max of 31 characters.", propName));
                                
                                W2A(propName);
                                char* newPropName = TABCleanFieldName(mbpropName);
                                if (strcmp(mbpropName, newPropName) == 0)
                                    throw FdoCommandException::Create(NlsMsgGetMain(MAPINFOPROVIDER_EXCEED_MAX_LENGTH_OF_PROPERTY_NAME, "Invalid property name: %1$ls. Property name can contain only letters, numbers, and '_'. It cannot contain spaces or punctuation marks, and it cannot begin with a number.", propName));
                            }
                        }
                    }
                }
            }
        }
    }
}
